module interp_mod
  use cam_logfile, only : iulog
  use shr_kind_mod, only : r8 => shr_kind_r8
  use dimensions_mod, only : nelemd, np
  use interpolate_mod, only : interpolate_scalar, setup_latlon_interp, set_interp_parameter, get_interp_lat, get_interp_lon, &
       var_is_vector_uvar, var_is_vector_vvar, interpolate_vector, interpdata_t, get_interp_gweight
  use dyn_grid,       only : elem
  use spmd_utils,       only : masterproc, iam
  use cam_pio_utils,  only: phys_decomp, fillvalue
  use commap, only : w
  use hybrid_mod,     only : hybrid_t, hybrid_create
  use abortutils, only: endrun

  implicit none
  private
  type(interpdata_t), pointer :: cam_interpolate(:)

  public get_interp_lat, get_interp_lon, setup_history_interpolation, write_interpolated
  public var_is_vector_uvar, var_is_vector_vvar, latlon_interpolation, add_interp_attributes

  interface write_interpolated
     module procedure write_interpolated_scalar
     module procedure write_interpolated_vector
  end interface
  type(hybrid_t) :: hybrid

contains

  subroutine add_interp_attributes(file)
    use pio, only : file_desc_t, pio_put_att, pio_global
    use interpolate_mod, only : get_interp_parameter
    type(file_desc_t) :: file

    integer :: ierr
    integer :: itmp
    
    itmp = get_interp_parameter('itype')
    if(itmp == 0) then
       ierr = pio_put_att(file, PIO_GLOBAL, 'interp_type', 'homme basis functions')
    else if(itmp == 1) then
       ierr = pio_put_att(file, PIO_GLOBAL, 'interp_type', 'bilinear')
    else
       ierr = pio_put_att(file, PIO_GLOBAL, 'interp_type', itmp)
    end if
    
    itmp = get_interp_parameter('gridtype')
    select case(itmp)
    case(1)
       ierr = pio_put_att(file, PIO_GLOBAL, 'interp_outputgridtype', 'equally spaced with poles')
    case(2)        
       ierr = pio_put_att(file, PIO_GLOBAL, 'interp_outputgridtype', 'Gauss')
    case(3)
       ierr = pio_put_att(file, PIO_GLOBAL, 'interp_outputgridtype', 'equally spaced no poles')
    case default
       ierr = pio_put_att(file, PIO_GLOBAL, 'interp_outputgridtype', itmp)
    end select


  end subroutine add_interp_attributes

  subroutine setup_history_interpolation(mtapes)

    use dyn_comp, only : dom_mt
    use parallel_mod,   only: par
    use thread_mod,     only: omp_get_thread_num
    use interpolate_mod, only : interpolate_analysis, get_interp_parameter
    implicit none
  
    integer, intent(in) :: mtapes
    integer :: ithr, nthreads

    if(iam>= par%nprocs) return

    ithr=omp_get_thread_num()
    hybrid = hybrid_create(par,ithr,1)
       
    if(any(interpolate_analysis(1:mtapes))) then
       allocate(cam_interpolate(nelemd))
       call setup_latlon_interp(elem, cam_interpolate, hybrid, 1, nelemd)
       allocate(w(get_interp_parameter('nlat')))
       w = get_interp_gweight()
    end if

  end subroutine setup_history_interpolation

  function latlon_interpolation(t)
    use interpolate_mod, only : interpolate_analysis
    integer, intent(in) :: t

    logical :: latlon_interpolation

    latlon_interpolation = interpolate_analysis(t)
  end function latlon_interpolation



  subroutine write_interpolated_scalar(File, varid, fld, numlev, data_type, decomp_type) 
    use pio, only : file_desc_t, io_desc_t, var_desc_t, pio_write_darray, iosystem_desc_t, pio_initdecomp, pio_freedecomp, pio_setdebuglevel
    use spmd_dyn,       only: local_dp_map, block_buf_nrecs, chunk_buf_nrecs
    use ppgrid, only : begchunk, endchunk, pcols, pver
    use phys_grid, only : get_gcol_all_p, get_ncols_p, chunk_to_block_send_pters, chunk_to_block_recv_pters, &
         transpose_chunk_to_block
    use dyn_grid,       only: get_gcol_block_d
    use dimensions_mod, only: npsq
    use element_mod, only : element_t
    use dof_mod, only : PutUniquePoints
    use interpolate_mod, only : get_interp_parameter
    use seq_io_mod, only : seq_io_getiosys
    use edge_mod, only : edgebuffer_t, edgevpack, edgevunpack, initedgebuffer, freeedgebuffer
    use bndry_mod, only : bndry_exchangeV
    use parallel_mod,   only: par
    use abortutils, only : endrun
    
    implicit none
    type(file_desc_t), intent(inout) :: File
    type(var_desc_t), intent(inout) :: varid
    real(r8), intent(in) :: fld(:,:,:)
    integer, intent(in) :: numlev, data_type, decomp_type

    type(io_desc_t) :: iodesc

    integer :: lchnk, i, j, m, icol, ncols, pgcols(pcols), ierr
    integer :: idmb1(1), idmb2(1), idmb3(1)
    integer :: bpter(npsq,0:pver)    ! offsets into block buffer for packing data
    integer :: cpter(pcols,0:pver)   ! offsets into chunk buffer for unpacking data

    real(r8), pointer :: dest(:,:,:,:) 
    real(r8), pointer :: bbuffer(:), cbuffer(:), fldout(:,:)
    real(r8) :: fld_dyn(npsq,numlev,nelemd)
    integer :: st, en, ie, ioff, ncnt_out, k
    integer, pointer :: idof(:)
    integer :: nlon, nlat, ncol
    logical :: usefillvalues=.false.
    type(iosystem_desc_t), pointer :: pio_subsystem
    type (EdgeBuffer_t) :: edgebuf              ! edge buffer



    nlon=get_interp_parameter('nlon')
    nlat=get_interp_parameter('nlat')
    pio_subsystem => seq_io_getiosys('ATM')

    if(decomp_type==phys_decomp) then
       fld_dyn = -999_R8
       if(local_dp_map) then
          !$omp parallel do private (lchnk, ncols, pgcols, icol, idmb1, idmb2, idmb3, ie, ioff, m)
          do lchnk=begchunk,endchunk
             ncols=get_ncols_p(lchnk)
             call get_gcol_all_p(lchnk,pcols,pgcols)
             do icol=1,ncols
                call get_gcol_block_d(pgcols(icol),1,idmb1,idmb2,idmb3)
                ie = idmb3(1)
                ioff=idmb2(1)
                do k=1,numlev
                   fld_dyn(ioff,k,ie)      = fld(icol, k, lchnk-begchunk+1)
                end do
             end do
             
          end do
       else

          allocate( bbuffer(block_buf_nrecs*numlev) )
          allocate( cbuffer(chunk_buf_nrecs*numlev) )

          !$omp parallel do private (lchnk, ncols, cpter, i, icol)
          do lchnk = begchunk,endchunk
             ncols = get_ncols_p(lchnk)
             
             call chunk_to_block_send_pters(lchnk,pcols,pver+1,1,cpter)
             
             do i=1,ncols
                cbuffer(cpter(i,1):cpter(i,1)) = 0.0_r8
             end do

             do icol=1,ncols
                
                cbuffer   (cpter(icol,:))     = fld(icol,:,lchnk-begchunk+1)
             end do

          end do

          call transpose_chunk_to_block(1, cbuffer, bbuffer)
          if(iam < par%nprocs) then
!$omp parallel do private (ie, bpter, icol)
             do ie=1,nelemd
          
                call chunk_to_block_recv_pters(elem(ie)%GlobalID,npsq,pver+1,1,bpter)
                ncols = elem(ie)%idxp%NumUniquePts
                do icol=1,ncols
                   fld_dyn   (icol,:,ie)   = bbuffer(bpter(icol,:))
                end do

             end do
          end if
          deallocate( bbuffer )
          deallocate( cbuffer )

       end if
       allocate(dest(np,np,numlev,nelemd))
       call initEdgeBuffer(edgebuf, numlev)

       do ie=1,nelemd
          ncols = elem(ie)%idxp%NumUniquePts
          call putUniquePoints(elem(ie)%idxP, numlev, fld_dyn(1:ncols,:,ie), dest(:,:,:,ie))
          call edgeVpack(edgebuf, dest(:,:,:,ie), numlev, 0, elem(ie)%desc)
       enddo
       if(iam < par%nprocs) then
          call bndry_exchangeV(par, edgebuf)
       end if
       do ie=1,nelemd
          call edgeVunpack(edgebuf, dest(:,:,:,ie), numlev, 0, elem(ie)%desc)
       end do
       call freeEdgeBuffer(edgebuf)
       usefillvalues = any(dest == fillvalue)
    else
        usefillvalues=any(fld==fillvalue)
       allocate(dest(np,np,numlev,1))
    end if

    ncnt_out = sum(cam_interpolate(1:nelemd)%n_interp)
    allocate(fldout(ncnt_out,numlev))
    allocate(idof(ncnt_out*numlev))
    fldout = -999_r8
    idof = 0
    st = 1
    
    

    do ie=1,nelemd
       ncol = cam_interpolate(ie)%n_interp
       do k=0,numlev-1
          do i=1,ncol
             idof(st+i-1+k*ncnt_out)=cam_interpolate(ie)%ilon(i)+nlon*(cam_interpolate(ie)%ilat(i)-1)+nlon*nlat*k
          enddo
       enddo
       
       ! Now that we have the field on the dyn grid we need to interpolate
       en = st+cam_interpolate(ie)%n_interp-1
       if(decomp_type==phys_decomp) then
          if(usefillvalues) then
             call interpolate_scalar(cam_interpolate(ie),dest(:,:,:,ie), np, numlev, fldout(st:en,:), fillvalue) 
          else
             call interpolate_scalar(cam_interpolate(ie),dest(:,:,:,ie), np, numlev, fldout(st:en,:)) 
          end if
       else
          do j=1,np
             do i=1,np
                dest(i,j,:,1) = fld(i+(j-1)*np,:,ie)
             end do
          end do
          if(usefillvalues) then
             call interpolate_scalar(cam_interpolate(ie),dest(:,:,:,1), &
                  np, numlev, fldout(st:en,:), fillvalue) 
          else
             call interpolate_scalar(cam_interpolate(ie),dest(:,:,:,1), &
                  np, numlev, fldout(st:en,:)) 
          end if
       end if


       st = en+1
    end do


    if(numlev==1) then
       call pio_initdecomp(pio_subsystem, data_type, (/nlon,nlat/), idof, iodesc)
    else
       call pio_initdecomp(pio_subsystem, data_type, (/nlon,nlat,numlev/), idof, iodesc)
    end if
    call pio_write_darray(File, varid, iodesc, fldout, ierr)

    deallocate(dest)

    deallocate(fldout)
    deallocate(idof)
    call pio_freedecomp(file,iodesc)

  end subroutine write_interpolated_scalar




  subroutine write_interpolated_vector(File, varidu, varidv, fldu, fldv, numlev, data_type, decomp_type) 
    use pio, only : file_desc_t, io_desc_t, var_desc_t, pio_write_darray, iosystem_desc_t, pio_initdecomp, pio_freedecomp, pio_setdebuglevel
    use spmd_dyn,       only: local_dp_map, block_buf_nrecs, chunk_buf_nrecs
    use ppgrid, only : begchunk, endchunk, pcols, pver
    use phys_grid, only : get_gcol_all_p, get_ncols_p, chunk_to_block_send_pters, chunk_to_block_recv_pters, &
         transpose_chunk_to_block
    use dyn_grid,       only: get_gcol_block_d
    use dimensions_mod, only: npsq
    use element_mod, only : element_t
    use dof_mod, only : PutUniquePoints
    use interpolate_mod, only : get_interp_parameter
    use seq_io_mod, only : seq_io_getiosys
    use edge_mod, only : edgebuffer_t, edgevpack, edgevunpack, initedgebuffer, freeedgebuffer
    use bndry_mod, only : bndry_exchangeV
    use parallel_mod,   only: par
    implicit none
    type(file_desc_t), intent(inout) :: File
    type(var_desc_t), intent(inout) :: varidu, varidv
    real(r8), intent(in) :: fldu(:,:,:), fldv(:,:,:)
    integer, intent(in) :: numlev, data_type, decomp_type

    type(io_desc_t) :: iodesc

    integer :: lchnk, i, j, m, icol, ncols, pgcols(pcols), ierr
    integer :: idmb1(1), idmb2(1), idmb3(1)
    integer :: bpter(npsq,0:pver)    ! offsets into block buffer for packing data
    integer :: cpter(pcols,0:pver)   ! offsets into chunk buffer for unpacking data

    real(r8), allocatable :: dest(:,:,:,:,:)
    real(r8), pointer :: bbuffer(:), cbuffer(:), fldout(:,:,:)
    real(r8) :: fld_dyn(npsq,2,numlev,nelemd)
    integer :: st, en, ie, ioff, ncnt_out, k
    integer, pointer :: idof(:)
    integer :: nlon, nlat, ncol
    logical :: usefillvalues=.false.

    type(iosystem_desc_t), pointer :: pio_subsystem
    type (EdgeBuffer_t) :: edgebuf              ! edge buffer



    nlon=get_interp_parameter('nlon')
    nlat=get_interp_parameter('nlat')
    pio_subsystem => seq_io_getiosys('ATM')
    fld_dyn = -999_R8
    if(decomp_type==phys_decomp) then
       allocate(dest(np,np,2,numlev,nelemd))
       if(local_dp_map) then
          !$omp parallel do private (lchnk, ncols, pgcols, icol, idmb1, idmb2, idmb3, ie, ioff, m)
          do lchnk=begchunk,endchunk
             ncols=get_ncols_p(lchnk)
             call get_gcol_all_p(lchnk,pcols,pgcols)
             
             do icol=1,ncols
                call get_gcol_block_d(pgcols(icol),1,idmb1,idmb2,idmb3)
                ie = idmb3(1)
                ioff=idmb2(1)
                do k=1,numlev
                   fld_dyn(ioff,1,k,ie)      = fldu(icol, k, lchnk-begchunk+1)
                   fld_dyn(ioff,2,k,ie)      = fldv(icol, k, lchnk-begchunk+1)
                end do
             end do
             
          end do
       else

          allocate( bbuffer(2*block_buf_nrecs*numlev) )
          allocate( cbuffer(2*chunk_buf_nrecs*numlev) )

          !$omp parallel do private (lchnk, ncols, cpter, i, icol)
          do lchnk = begchunk,endchunk
             ncols = get_ncols_p(lchnk)
             
             call chunk_to_block_send_pters(lchnk,pcols,pver+1,2,cpter)
             
             do i=1,ncols
                cbuffer(cpter(i,1):cpter(i,1)) = 0.0_r8
             end do

             do icol=1,ncols
                do k=1,numlev
                   cbuffer   (cpter(icol,k))     = fldu(icol,k,lchnk-begchunk+1)
                   cbuffer   (cpter(icol,k)+1)   = fldv(icol,k,lchnk-begchunk+1)
                end do
             end do

          end do

          call transpose_chunk_to_block(2, cbuffer, bbuffer)
          if(iam < par%nprocs) then
             !$omp parallel do private (ie, bpter, icol)
             do ie=1,nelemd
                
                call chunk_to_block_recv_pters(elem(ie)%GlobalID,npsq,pver+1,2,bpter)
                ncols = elem(ie)%idxp%NumUniquePts
                do icol=1,ncols
                   do k=1,numlev
                      fld_dyn   (icol,1,k,ie)   = bbuffer(bpter(icol,k))
                      fld_dyn   (icol,2,k,ie)   = bbuffer(bpter(icol,k)+1)
                   enddo
                end do
                
             end do
          end if
          deallocate( bbuffer )
          deallocate( cbuffer )

       end if
       call initEdgeBuffer(edgebuf, 2*numlev)

       do ie=1,nelemd
          ncols = elem(ie)%idxp%NumUniquePts
          call putUniquePoints(elem(ie)%idxP, 2, numlev, fld_dyn(1:ncols,:,:,ie), dest(:,:,:,:,ie))
          
          call edgeVpack(edgebuf, dest(:,:,:,:,ie), 2*numlev, 0, elem(ie)%desc)
       enddo
       if(iam < par%nprocs) then
          call bndry_exchangeV(par, edgebuf)
       end if

       do ie=1,nelemd
          call edgeVunpack(edgebuf, dest(:,:,:,:,ie), 2*numlev, 0, elem(ie)%desc)
       enddo
       call freeEdgeBuffer(edgebuf)
       usefillvalues = any(dest==fillvalue)
    else
       usefillvalues = (any(fldu==fillvalue) .or. any(fldv==fillvalue))
       allocate(dest(np,np,2,numlev,1))
    endif
    ncnt_out = sum(cam_interpolate(1:nelemd)%n_interp)
    allocate(fldout(ncnt_out,numlev,2))
    allocate(idof(ncnt_out*numlev))
    
    fldout = -999_r8
    idof = 0
    st = 1
    do ie=1,nelemd
       ncol = cam_interpolate(ie)%n_interp
       do k=0,numlev-1
          do i=1,ncol
             idof(st+i-1+k*ncnt_out)=cam_interpolate(ie)%ilon(i)+nlon*(cam_interpolate(ie)%ilat(i)-1)+nlon*nlat*k
          enddo
       enddo
       

    ! Now that we have the field on the dyn grid we need to interpolate
       en = st+cam_interpolate(ie)%n_interp-1
       if(decomp_type==phys_decomp) then
          if(usefillvalues) then
             call interpolate_vector(cam_interpolate(ie),elem(ie)%Dinv, elem(ie)%FaceNum,&
                  dest(:,:,:,:,ie), np, numlev, fldout(st:en,:,:), 0, fillvalue) 
          else
             call interpolate_vector(cam_interpolate(ie),elem(ie)%Dinv, elem(ie)%FaceNum,&
                  dest(:,:,:,:,ie), np, numlev, fldout(st:en,:,:), 0) 
          endif
       else
          do k=1,numlev
             do j=1,np
                do i=1,np
                   dest(i,j,1,k,1) = fldu(i+(j-1)*np,k,ie)
                   dest(i,j,1,k,1) = fldv(i+(j-1)*np,k,ie)
                end do
             end do
          end do
          if(usefillvalues) then
             call interpolate_vector(cam_interpolate(ie),elem(ie)%Dinv, elem(ie)%FaceNum,&
                  dest(:,:,:,:,1), np, numlev, fldout(st:en,:,:), 0, fillvalue) 
          else
             call interpolate_vector(cam_interpolate(ie),elem(ie)%Dinv, elem(ie)%FaceNum,&
                  dest(:,:,:,:,1), np, numlev, fldout(st:en,:,:), 0) 
          end if
       end if

       st = en+1
    end do

    if(numlev==1) then
       call pio_initdecomp(pio_subsystem, data_type, (/nlon,nlat/), idof, iodesc)
    else
       call pio_initdecomp(pio_subsystem, data_type, (/nlon,nlat,numlev/), idof, iodesc)
    end if

    call pio_write_darray(File, varidu, iodesc, fldout(:,:,1), ierr)

    call pio_write_darray(File, varidv, iodesc, fldout(:,:,2), ierr)


    deallocate(fldout)
    deallocate(idof)
    deallocate(dest)
    call pio_freedecomp(file,iodesc)

  end subroutine write_interpolated_vector












end module interp_mod

